#include "MarsPrecompile.h"

#include "MarsExample2.h"
#include "MarsRenderer.h"
#include "MarsGameObject.h"
#include "MarsMeshTriangle.h"
#include "MarsMaterial.h"
#include "MarsRenderable.h"
#include "MarsVertexShader.h"
#include "MarsPixelShader.h"
#include "MarsVertexData.h"
#include "MarsInputLayout.h"
#include "MarsVRamBuffer.h"
#include "MarsTexture.h"
#include "MarsSamplerState.h"
#include "MarsMeshCube.h"
#include "MarsConstantMgr.h"
#include "MarsCamera.h"
#include "MarsShaderMgr.h"
#include "MarsLight.h"
#include "MarsDirectionalLight.h"
#include "MarsBlendState.h"
#include "MarsRasterizerState.h"
#include "MarsRenderTarget.h"
#include "MarsMeshPlane.h"
#include "MarsMeshMgr.h"
#include "MarsPipelineType.h"

using namespace Mars::App;

static GameObject* s_pGameObject = NULL;
static GameObject* s_pPlaneGameObject = NULL;
static Camera*	   s_pCamera	 = NULL;
static Mesh*	   s_pMesh		 = NULL;
static Mesh*	   s_pPlaneMesh  = NULL;
static Material*   s_pMaterial	 = NULL;
static Material*   s_pShadowMaterial = NULL;
static DirectionalLight* s_pDirectionLight = NULL;

static VertexShader* s_pVertexShader = NULL;
static PixelShader*  s_pPixelShader  = NULL;

static VertexShader* s_pShadowVertexShader = NULL;

static Texture*		 s_pDiffuseMap = NULL;
static SamplerState* s_pDiffuseSS = NULL;

static BlendState* s_pBlendState = NULL;
static RasterizerState* s_pRasterizerState = NULL;

static RenderTarget* s_pShadowMap = NULL;
static SamplerState* s_pShadowSS = NULL;
static RasterizerState* s_pShadowRS = NULL;

static XMMATRIX s_shadowTransform;

static void CleanupStaticObject()
{
	SAFE_DELETE( s_pDirectionLight );
	SAFE_DELETE( s_pGameObject );
	SAFE_DELETE( s_pPlaneGameObject );
	SAFE_DELETE( s_pCamera );
	SAFE_DELETE( s_pMesh );
	SAFE_DELETE( s_pPlaneMesh );
	SAFE_DELETE( s_pMaterial );
	SAFE_DELETE( s_pShadowMaterial );
	SAFE_DELETE( s_pDiffuseMap );
	SAFE_DELETE( s_pDiffuseSS );
	SAFE_DELETE( s_pBlendState );
	SAFE_DELETE( s_pRasterizerState );
	SAFE_DELETE( s_pCamera );
	SAFE_DELETE( s_pShadowMap );
	SAFE_DELETE( s_pShadowSS );
	SAFE_DELETE( s_pShadowRS );
}

Example2::Example2()
{

}

Example2::~Example2()
{

}

bool Example2::LoadContent()
{
	bool bSuccess = false;

	struct CleanupObject
	{
		CleanupObject(bool* pSuccuss) : m_pSuccess( pSuccuss )
		{}

		~CleanupObject() 
		{
			if ( !*m_pSuccess )
			{
				CleanupStaticObject();
			}
		}

		bool* m_pSuccess;
	} cleanupObject( &bSuccess );

	s_pGameObject = new GameObject();
	s_pPlaneGameObject = new GameObject();

	s_pMesh = new MeshCube();

	if ( !s_pMesh->Load() )
		return false;

	s_pPlaneMesh = new MeshPlane();

	if ( !s_pPlaneMesh->Load() )
		return false;

	s_pVertexShader = static_cast< VertexShader* > ( ShaderMgr::Instance().LoadShader( 
		ShaderType_Vertex, 
		"res\\shaders\\DirectionalLightTexShadow.fx", 
		"VS_Main", 
		"vs_4_0" ) );

	if ( !s_pVertexShader )
		return false;

	s_pPixelShader = static_cast< PixelShader* > ( ShaderMgr::Instance().LoadShader( 
		ShaderType_Pixel, 
		"res\\shaders\\DirectionalLightTexShadow.fx", 
		"PS_Main", 
		"ps_4_0" ) );

	if ( !s_pPixelShader )
		return false;

	s_pShadowVertexShader = static_cast< VertexShader* > ( ShaderMgr::Instance().LoadShader( 
		ShaderType_Vertex, 
		"res\\shaders\\ShadowMap.fx", 
		"VS_Main", 
		"vs_4_0" ) );

	if ( !s_pShadowVertexShader )
		return false;

	s_pMesh->GetVertexData()->GetInputLayout()->Bind( *s_pVertexShader );
	s_pPlaneMesh->GetVertexData()->GetInputLayout()->Bind( * s_pVertexShader );

	s_pDiffuseMap = new Texture( Texture::TT_2D, "res\\textures\\decal.dds" );
	if ( !s_pDiffuseMap->LoadTexture() )
		return false;

	D3D11_SAMPLER_DESC samplerDesc;
	MARS_MEMSET( &samplerDesc, 0, sizeof( D3D11_SAMPLER_DESC ) );
	samplerDesc.AddressU	   = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.AddressV	   = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.AddressW	   = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
	samplerDesc.Filter		   = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
	samplerDesc.MaxLOD		   = D3D11_FLOAT32_MAX;

	s_pDiffuseSS = new SamplerState( samplerDesc );
	if ( !s_pDiffuseSS->Create() )
		return false;

	D3D11_SAMPLER_DESC samplerCmpDesc;
	MARS_MEMSET( &samplerCmpDesc, 0, sizeof( D3D11_SAMPLER_DESC ) );
	samplerCmpDesc.AddressU	   = D3D11_TEXTURE_ADDRESS_BORDER;
	samplerCmpDesc.AddressV	   = D3D11_TEXTURE_ADDRESS_BORDER;
	samplerCmpDesc.AddressW	   = D3D11_TEXTURE_ADDRESS_BORDER;
	samplerCmpDesc.ComparisonFunc = D3D11_COMPARISON_LESS;
	samplerCmpDesc.Filter		   = D3D11_FILTER_COMPARISON_MIN_MAG_POINT_MIP_LINEAR;
	samplerCmpDesc.MaxLOD		   = D3D11_FLOAT32_MAX;
	samplerCmpDesc.BorderColor[0] = 0.0f;
	samplerCmpDesc.BorderColor[1] = 0.0f;
	samplerCmpDesc.BorderColor[2] = 0.0f;
	samplerCmpDesc.BorderColor[3] = 0.0f;

	s_pShadowSS = new SamplerState( samplerCmpDesc );
	if ( !s_pShadowSS->Create() )
		return false;

	D3D11_BLEND_DESC blendDesc;
	blendDesc.AlphaToCoverageEnable = false;
	blendDesc.IndependentBlendEnable = false;
	blendDesc.RenderTarget[ 0 ].BlendEnable = false;
	blendDesc.RenderTarget[ 0 ].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
	s_pBlendState = new BlendState( blendDesc );
	if ( !s_pBlendState->Create() )
		return false;

	D3D11_RASTERIZER_DESC rasterizerDesc;
	MARS_MEMSET( &rasterizerDesc, 0, sizeof( D3D11_RASTERIZER_DESC ) );
	rasterizerDesc.FillMode = D3D11_FILL_SOLID;
	rasterizerDesc.CullMode = D3D11_CULL_BACK;
	rasterizerDesc.FrontCounterClockwise = false;
	rasterizerDesc.DepthClipEnable = true;
	s_pRasterizerState = new RasterizerState( rasterizerDesc );
	if ( !s_pRasterizerState->Create() )
		return false;

	s_pMaterial = new Material();
	s_pMaterial->SetVertexShader( s_pVertexShader );
	s_pMaterial->SetPixelShader( s_pPixelShader );
	s_pMaterial->SetDiffuseMap( s_pDiffuseMap, s_pDiffuseSS );
	s_pMaterial->SetBlendState( s_pBlendState );
	s_pMaterial->SetRasterizerState( s_pRasterizerState );

	s_pMaterial->SetAmbientColor( XMFLOAT4( 0.3f, 0.3f, 0.3f, 1.0f ) );
	s_pMaterial->SetDiffuseColor( XMFLOAT4( 1.0f, 1.0f, 1.0f, 1.0f ) );
	s_pMaterial->SetSpecularColor( XMFLOAT4( 0.0f, 0.0f, 0.0f, 1.0f ) );
	s_pMaterial->SetShininess( 1.0f );

	s_pShadowMaterial = new Material();
	s_pShadowMaterial->SetVertexShader( s_pShadowVertexShader );

	D3D11_RASTERIZER_DESC shadowRasterizerDesc;
	MARS_MEMSET( &shadowRasterizerDesc, 0, sizeof( D3D11_RASTERIZER_DESC ) );
	shadowRasterizerDesc.FillMode = D3D11_FILL_SOLID;
	shadowRasterizerDesc.CullMode = D3D11_CULL_NONE;
	shadowRasterizerDesc.FrontCounterClockwise = false;
	shadowRasterizerDesc.DepthClipEnable = true;
	shadowRasterizerDesc.DepthBias = 100000;
	shadowRasterizerDesc.DepthBiasClamp = 0.0f;
	shadowRasterizerDesc.SlopeScaledDepthBias = 1.0f;
	s_pShadowRS = new RasterizerState( shadowRasterizerDesc );
	if ( !s_pShadowRS->Create() )
		return false;

	s_pShadowMaterial->SetRasterizerState( s_pShadowRS );

	Renderable* pRenderable = new Renderable( s_pMesh, s_pMaterial );
	s_pGameObject->AddComponent( pRenderable );

	pRenderable = new Renderable( s_pPlaneMesh, s_pMaterial );
	s_pPlaneGameObject->AddComponent( pRenderable );

	s_pCamera = new Camera( Camera::PT_PERSPECTIVE );

	s_pDirectionLight = new DirectionalLight( XMFLOAT3( 0.0f, -1.0f, 0.0f ) );

	s_pDirectionLight->SetPosition( XMFLOAT3( 0.0f, 3.0f, 0.0f ) );
	s_pDirectionLight->SetTarget( XMFLOAT3( 0.0f, 0.0f, 0.0f ) );

	s_pShadowMap = new RenderTarget( 1024, 1024 );
	if ( !s_pShadowMap->Create() )
		return false;

	MeshMgr::Instance().LoadMesh( "C:\\Projects\\MarsEngine\\MarsEngine\\res\\mesh\\nano.fbx",  MIT_Fbx );

	bSuccess = true;
	return bSuccess;
}

void Example2::UnloadContent()
{
	CleanupStaticObject();
}

void Example2::Update( float dt )
{
	s_pCamera->SetPosition( XMFLOAT3( 0.0f, 3.0f, -15.0f ), XMFLOAT3( 0.0f, 0.0f, 0.0f ) );
}

void Example2::Render()
{
	if ( !Renderer::Instance().IsReady() )
		return;

	RenderToShadowMap();

	float clearColor[4] = { 0.0f, 0.0f, 1.0f, 1.0f };
	Renderer::Instance().ClearRenderTarget( clearColor );
	Renderer::Instance().ClearDepthStencil( 1.0f, 0 );

 	XMMATRIX rotationMat = XMMatrixRotationRollPitchYaw( 0.0f, 0.0f, 0.0f );
 	XMMATRIX translationMat = XMMatrixTranslation( 0.0f, 0.0f, 0.0f );
 
 	XMMATRIX worldMatrix = rotationMat * translationMat;
 	RenderGameObject( s_pGameObject, worldMatrix );

	rotationMat = XMMatrixRotationRollPitchYaw( 0.0f, 0.0f, 0.0f );
	translationMat = XMMatrixTranslation( 0.0f, -5.0f, 0.0f );

	worldMatrix = rotationMat * translationMat;
	RenderGameObject( s_pPlaneGameObject, worldMatrix );

	Renderer::Instance().Present();
}

void Example2::RenderGameObject( GameObject* pGameObject, XMMATRIX& worldMatrix )
{
	if ( pGameObject && pGameObject->IsRenderable() )
	{
		ID3D11DeviceContext* pContext = Renderer::Instance().GetDeviceContext();

		Renderable* pRenderable = pGameObject->GetRenderable();
		Mesh* pMesh = pRenderable->GetMesh();
		Material* pMaterial = pRenderable->GetMaterial();
		VertexData* pVertexData = pMesh->GetVertexData();

		pContext->IASetInputLayout( pVertexData->GetInputLayout()->GetNativeInputLayout() );

		ID3D11Buffer* pBuffer = pVertexData->GetVertexBuffer()->GetNativeBuffer();
		uint32 iStride = pMesh->GetStride();
		uint32 iOffset = pMesh->GetOffset();
		pContext->IASetVertexBuffers( 0, 1, &pBuffer, &iStride, &iOffset );

		if ( pVertexData->IsContainIndexBuffer() )
		{
			ID3D11Buffer* pIndexBuffer = pVertexData->GetIndexBuffer()->GetNativeBuffer();
			DXGI_FORMAT eFormat = pVertexData->GetIndexBufferFormat();
			pContext->IASetIndexBuffer( pIndexBuffer, eFormat, 0 );
		}

		pContext->IASetPrimitiveTopology( pMesh->GetTopology() );

		pContext->VSSetShader( pMaterial->GetVertexShader()->GetNativeVertexShader(), 0, 0 );
		pContext->PSSetShader( pMaterial->GetPixelShader()->GetNativePixelShader(), 0, 0 );

		ID3D11SamplerState* pDiffuseSS = pMaterial->GetDiffuseSamplerState()->GetNativeSamplerState();
		pContext->PSSetSamplers( 0, 1, &pDiffuseSS );
		ID3D11ShaderResourceView* pDiffuseMapSRV = pMaterial->GetDiffuseMap()->GetNativeTextureSRV();
		pContext->PSSetShaderResources( 0, 1, &pDiffuseMapSRV );

 		ID3D11SamplerState* pShadowSS = s_pShadowSS->GetNativeSamplerState();
 		pContext->PSSetSamplers( 1, 1, &pShadowSS );
 		ID3D11ShaderResourceView* pShadowMapSRV = s_pShadowMap->GetNativeTextureSRV();
 		pContext->PSSetShaderResources( 1, 1, &pShadowMapSRV );

 		ID3D11BlendState* pBlendState = pMaterial->GetBlendState()->GetNativeBlendState();
 		float blendFactor[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
 		pContext->OMSetBlendState( pBlendState, blendFactor, 0xffffffff );

		ID3D11RasterizerState* pRasterizerState = pMaterial->GetRasterizerState()->GetNativeRasterizerState();
		pContext->RSSetState( pRasterizerState );

		XMMATRIX viewMatrix = s_pCamera->GetViewMatrix();
		XMMATRIX projMatrix = s_pCamera->GetProjMatrix();
		XMMATRIX worldViewProjMatrix = worldMatrix * viewMatrix * projMatrix;

		XMMATRIX worldInvTranspose = worldMatrix;
		worldInvTranspose.r[ 3 ] = XMVectorSet( 0.0f, 0.0f, 0.0f, 1.0f );
		XMVECTOR det = XMMatrixDeterminant( worldInvTranspose );
		worldInvTranspose = XMMatrixTranspose( XMMatrixInverse( &det, worldInvTranspose ) );
		worldInvTranspose = XMMatrixTranspose( worldInvTranspose );

		XMMATRIX shadowMat = worldMatrix * s_shadowTransform;

		worldMatrix = XMMatrixTranspose( worldMatrix );
		viewMatrix = XMMatrixTranspose( viewMatrix );
		projMatrix = XMMatrixTranspose( projMatrix );

		worldViewProjMatrix = XMMatrixTranspose( worldViewProjMatrix );

		shadowMat = XMMatrixTranspose( shadowMat );

		XMFLOAT3 eyePos = s_pCamera->GetPosition();
		XMFLOAT4 pos( eyePos.x, eyePos.y, eyePos.z, 0.0f );

		pMaterial->SetParameter( SS_EyeWorldPosition, &pos );
		pMaterial->SetParameter( SS_WorldMatrix, &worldMatrix );
		pMaterial->SetParameter( SS_ViewMatrix, &viewMatrix );
		pMaterial->SetParameter( SS_ProjMatrix, &projMatrix );
		pMaterial->SetParameter( SS_WorldViewProjMatrix, &worldViewProjMatrix );
		pMaterial->SetParameter( SS_WorldInvTransposeMatrix, &worldInvTranspose );
		pMaterial->SetParameter( SS_ShadowTransform, &shadowMat );

		ShaderDirectionalLight litParam;
		litParam.Ambient = s_pDirectionLight->GetAmbientColor();
		litParam.Diffuse = s_pDirectionLight->GetDiffuseColor();
		litParam.Specular = s_pDirectionLight->GetSpecularColor();
		litParam.Direction = s_pDirectionLight->GetDirection();
		litParam.Pad = 0.0f;

		pMaterial->SetParameter( SS_DirectionalLight, &litParam );

		pMaterial->Bind();

		if ( pVertexData->IsContainIndexBuffer() )
		{
			pContext->DrawIndexed( pMesh->GetVertexCount(), 0, 0 );
		}
		else
		{
			pContext->Draw( pMesh->GetVertexCount(), 0 );
		}

		pMaterial->Unbind();
		pContext->RSSetState( NULL );
	}
}

void Example2::RenderToShadowMap()
{
	Renderer::Instance().PushRenderTarget( s_pShadowMap );

	const float LIGHT_RENDER_RADIUS = 30.0f;

	// Only the first "main" light casts a shadow.
	XMVECTOR lightPos = XMLoadFloat3( &s_pDirectionLight->GetPosition() );
	XMVECTOR targetPos = XMLoadFloat3( &s_pDirectionLight->GetTarget() );
	XMVECTOR up = XMVectorSet( 0.0f, 0.0f, 1.0f, 0.0f );

	XMMATRIX lightViewMat = XMMatrixLookAtLH( lightPos, targetPos, up );

	// Transform bounding sphere to light space.
	XMFLOAT3 sphereCenterLS;
	XMStoreFloat3( &sphereCenterLS, XMVector3TransformCoord( targetPos, lightViewMat ) );
	
	// Ortho frustum in light space encloses scene.
	float l = sphereCenterLS.x - LIGHT_RENDER_RADIUS;
	float b = sphereCenterLS.y - LIGHT_RENDER_RADIUS;
	float n = sphereCenterLS.z - LIGHT_RENDER_RADIUS;
	float r = sphereCenterLS.x + LIGHT_RENDER_RADIUS;
	float t = sphereCenterLS.y + LIGHT_RENDER_RADIUS;
	float f = sphereCenterLS.z + LIGHT_RENDER_RADIUS;
	XMMATRIX lightPMat = XMMatrixOrthographicOffCenterLH( l, r, b, t, n, f );

	XMMATRIX lightViewProjMat = XMMatrixMultiply( lightViewMat, lightPMat );

	XMMATRIX lightTexMat(
		0.5f,  0.0f, 0.0f, 0.0f,
		0.0f, -0.5f, 0.0f, 0.0f,
		0.0f,  0.0f, 1.0f, 0.0f,
		0.5f,  0.5f, 0.0f, 1.0f);

	s_shadowTransform = lightViewProjMat * lightTexMat;

	XMMATRIX rotationMat = XMMatrixRotationRollPitchYaw( 0.0f, 0.0f, 0.0f );
	XMMATRIX translationMat = XMMatrixTranslation( 0.0f, 0.0f, 0.0f );
	XMMATRIX worldMatrix = rotationMat * translationMat;
	XMMATRIX worldViewProjMatrix = worldMatrix * lightViewProjMat;
	RenderGameObjectToShadowMap( s_pGameObject, worldViewProjMatrix );

	rotationMat = XMMatrixRotationRollPitchYaw( 0.0f, 0.0f, 0.0f );
	translationMat = XMMatrixTranslation( 0.0f, -5.0f, 0.0f );
	worldMatrix = rotationMat * translationMat;
	worldViewProjMatrix = worldMatrix * lightViewProjMat;
	RenderGameObjectToShadowMap( s_pPlaneGameObject, worldViewProjMatrix );

	Renderer::Instance().PopRenderTarget();
}

void Example2::RenderGameObjectToShadowMap( GameObject* pGameObject, XMMATRIX& worldViewProjMatrix )
{
	if ( pGameObject && pGameObject->IsRenderable() )
	{
		ID3D11DeviceContext* pContext = Renderer::Instance().GetDeviceContext();

		Renderable* pRenderable = pGameObject->GetRenderable();
		Mesh* pMesh = pRenderable->GetMesh();
		VertexData* pVertexData = pMesh->GetVertexData();

		pContext->IASetInputLayout( pVertexData->GetInputLayout()->GetNativeInputLayout() );

		ID3D11Buffer* pBuffer = pVertexData->GetVertexBuffer()->GetNativeBuffer();
		uint32 iStride = pMesh->GetStride();
		uint32 iOffset = pMesh->GetOffset();
		pContext->IASetVertexBuffers( 0, 1, &pBuffer, &iStride, &iOffset );

		if ( pVertexData->IsContainIndexBuffer() )
		{
			ID3D11Buffer* pIndexBuffer = pVertexData->GetIndexBuffer()->GetNativeBuffer();
			DXGI_FORMAT eFormat = pVertexData->GetIndexBufferFormat();
			pContext->IASetIndexBuffer( pIndexBuffer, eFormat, 0 );
		}

		pContext->IASetPrimitiveTopology( pMesh->GetTopology() );

		pContext->VSSetShader( s_pShadowMaterial->GetVertexShader()->GetNativeVertexShader(), 0, 0 );

		if ( s_pShadowMaterial->GetPixelShader() )
			pContext->PSSetShader( s_pShadowMaterial->GetPixelShader()->GetNativePixelShader(), 0, 0 );
		else
			pContext->PSSetShader( NULL, 0, 0 );

		worldViewProjMatrix = XMMatrixTranspose( worldViewProjMatrix );

		XMFLOAT3 eyePos = s_pCamera->GetPosition();
		XMFLOAT4 pos( eyePos.x, eyePos.y, eyePos.z, 0.0f );
		//s_pShadowMaterial->SetParameter( SS_EyeWorldPosition, &pos );
		//s_pShadowMaterial->SetParameter( SS_WorldMatrix, &worldMatrix );
		//s_pShadowMaterial->SetParameter( SS_ViewMatrix, &lightViewMat );
		//s_pShadowMaterial->SetParameter( SS_ProjMatrix, &lightPMat );
		s_pShadowMaterial->SetParameter( SS_WorldViewProjMatrix, &worldViewProjMatrix );
// 
// 		XMMATRIX A = worldMatrix;
// 		A.r[ 3 ] = XMVectorSet( 0.0f, 0.0f, 0.0f, 1.0f );
// 		XMVECTOR det = XMMatrixDeterminant( A );
// 		A = XMMatrixTranspose( XMMatrixInverse( &det, A ) );
		//s_pShadowMaterial->SetParameter( SS_WorldInvTransposeMatrix, &A );

		ID3D11RasterizerState* pRasterizerState = s_pShadowMaterial->GetRasterizerState()->GetNativeRasterizerState();
		pContext->RSSetState( pRasterizerState );

		s_pShadowMaterial->Bind();

		if ( pVertexData->IsContainIndexBuffer() )
		{
			pContext->DrawIndexed( pMesh->GetVertexCount(), 0, 0 );
		}
		else
		{
			pContext->Draw( pMesh->GetVertexCount(), 0 );
		}

		s_pShadowMaterial->Unbind();
		pContext->RSSetState( NULL );
	}
}
